package com.github.eclipseace.mybatis.spring;

import java.io.IOException;
import java.util.List;

import org.apache.ibatis.logging.Log;
import org.apache.ibatis.logging.LogFactory;
import org.apache.ibatis.session.Configuration;
import org.apache.ibatis.session.SqlSessionFactory;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.context.ResourceLoaderAware;
import org.springframework.core.io.ResourceLoader;
import org.springframework.core.type.classreading.MetadataReader;
import org.springframework.core.type.filter.AnnotationTypeFilter;
import org.springframework.util.Assert;

import com.github.eclipseace.mybatis.entity.builder.ElementBuilder;
import com.github.eclipseace.mybatis.entity.mapper.ApplyEntity;
import com.github.eclipseace.mybatis.entity.mapper.MapperEnhancer;
import com.github.eclipseace.mybatis.entity.mapping.EntityResolver;

/**
 * @author eclipseAce
 */
public class MapperEnhancerConfigurer implements InitializingBean, ResourceLoaderAware {
	protected final Log log = LogFactory.getLog(getClass());

	private Configuration configuration;
	private EntityResolver entityResolver;
	private List<ElementBuilder> elementBuilders;

	private EntityStore entityStore;
	private ResourceLoader resourceLoader;
	private String[] basePackages;

	protected MapperEnhancer createEnhancer() {
		MapperEnhancer enhancer = new MapperEnhancer(configuration, entityResolver);
		if (elementBuilders != null) {
			enhancer.addElementBuilders(elementBuilders);
		}
		for (String entityName : entityStore.getNames()) {
			enhancer.addEntityType(entityStore.getResolvedType(entityName, null));
		}
		return enhancer;
	}

	protected ClassPathScanner createScanner() {
		ClassPathScanner scanner = new ClassPathScanner(resourceLoader, null);
		scanner.addTypeFilter(new AnnotationTypeFilter(ApplyEntity.class));
		return scanner;
	}

	protected void doConfigureMappers() {
		MapperEnhancer enhancer = createEnhancer();
		ClassPathScanner scanner = createScanner();
		for (String pkg : basePackages) {
			try {
				for (MetadataReader metadataReader : scanner.scan(pkg)) {
					String clsName = metadataReader.getClassMetadata().getClassName();
					try {
						enhancer.enhance(Class.forName(clsName));

						if (log.isDebugEnabled()) {
							log.debug("Mapper enhanced [" + clsName + "]");
						}
					} catch (Throwable e) {
						log.error("Skip mapper [" + clsName + "] due to a failure during enhancing", e);
					}
				}
			} catch (IOException e) {
				log.error("Skip mappers in package [" + pkg + "] due to a I/O exception", e);
			}
		}
	}

	@Override
	public void afterPropertiesSet() throws Exception {
		Assert.notNull(configuration, "Property 'sqlSessionFactory' or 'configuration' is required");
		Assert.notNull(entityStore, "Property 'entityStore' is required");
		Assert.notEmpty(basePackages, "Property 'basePackages' is required");

		doConfigureMappers();
	}

	public void setSqlSessionFactory(SqlSessionFactory sqlSessionFactory) {
		if (sqlSessionFactory != null) {
			this.configuration = sqlSessionFactory.getConfiguration();
		}
	}

	public void setEntityStore(EntityStore entityStore) {
		this.entityStore = entityStore;
	}

	public void setEntityResolver(EntityResolver entityResolver) {
		this.entityResolver = entityResolver;
	}

	public void setElementBuilders(List<ElementBuilder> elementBuilders) {
		this.elementBuilders = elementBuilders;
	}

	@Override
	public void setResourceLoader(ResourceLoader resourceLoader) {
		this.resourceLoader = resourceLoader;
	}

	public void setBasePackages(String[] basePackages) {
		this.basePackages = basePackages;
	}
}
